using System;
using Microsoft.SPOT;
using System.IO;
using System.Threading;

namespace nRF24L10P.Roles
{
    public class SendReceiveRole: IRole
    {
        nRF24L10P device;
        public bool IsRunning { get; private set; }
        //private byte[] sendBuffer = new byte[512];
        //private byte[] receiveBuffer = new byte[512];
        //private int receiveBufferLength = 0;
        //private int sendBufferLength = 0;
        //private int receiveBufferReadPosition = 0;
        DataReadPipe reader;
        DataWritePipe writer;
        public delegate void DataArrivedDelegate(byte[] data);
        public event DataArrivedDelegate DataArrived=null;

        //public delegate void SendCompleteDelegate(bool success);
        //public event SendCompleteDelegate SendComplete;

        //public bool SendFailed { get; private set; }
        //public bool SendSuccess { get; private set; }

        private bool isSending = false;
        private bool isMaxRT = false;

        private ManualResetEvent sendCompleteEvent;

        private object syncRoot ;

        public SendReceiveRole()
        {
            IsRunning = false;
            sendCompleteEvent = new ManualResetEvent(false);
            syncRoot = new object();
        }
        public void AttachDevice(nRF24L10P device)
        {
            this.device = device;
        }

        public void DetachDevice()
        {
            this.device = null;
        }

        private byte[] sendAddress;

        public byte[] SendAddress
        {
            get { return sendAddress; }
            set 
            {
                if (IsRunning)
                {
                    throw new InvalidOperationException("SendAddress is readonly while Running");
                }
                sendAddress = value; 
            }
        }

        private byte[] receiveAddress;

        public byte[] ReceiveAddress
        {
            get { return receiveAddress; }
            set 
            {
                if (IsRunning)
                {
                    throw new InvalidOperationException("ReceiveAddress is readonly while Running");
                }
                receiveAddress = value; 
            }
        }


        public void EnableReceivePipe(int pipeID, byte address)
        {
            if (IsRunning)
            {
                throw new InvalidOperationException("Please call this method before you call the Start method");
            }
            if (pipeID>5 || pipeID<2)
            {
                throw new ArgumentOutOfRangeException("pipID", "pipID should be 2-5");
            }
            device.RXPipes[pipeID].Address = new byte[1]{address};
            device.RXPipes[pipeID].AutoACKEnabled = true;
            device.RXPipes[pipeID].DynamicPayloadLengthEnabled = true;
            device.RXPipes[pipeID].Enabled = true;
        }

        public void DisableReceivePipe(int pipeID)
        {
            if (IsRunning)
            {
                throw new InvalidOperationException("Please call this method before you call the Start method");
            }
            if (pipeID > 5 || pipeID < 2)
            {
                throw new ArgumentOutOfRangeException("pipID", "pipID should be 2-5");
            }
            device.RXPipes[pipeID].Enabled = false;
        }


        public void Start()
        {
            if (IsRunning)
            {
                return;
            }
            if (SendAddress==null || ReceiveAddress==null)
            {
                throw new InvalidOperationException("please set the SendAddress and ReceiveAddress before Start");
            }

            //shut down device for config
            device.Status = DeviceStatusEnum.StandBy;

            

            //enable dynamic package length by default
            device.Config.DynamicPayloadLengthEnabled = true;
            
            //config auto ACK answer receiving pipe
            device.RXPipes[0].AutoACKEnabled = true;
            device.RXPipes[0].DynamicPayloadLengthEnabled = true;
            device.RXPipes[0].Address = this.SendAddress;
            device.RXPipes[0].Enabled = true;

            writer = device.TXPipe;
            writer.Address = this.SendAddress;

            //config receiver
            reader = device.RXPipes[1];
            reader.AutoACKEnabled = true;
            reader.DynamicPayloadLengthEnabled = true;
            reader.Address = this.ReceiveAddress;
            reader.Enabled = true;

            device.FlushRX();
            device.FlushTX();
            //attach event handler
            device.OnInterrupt += device_OnInterrupt;

            //put to RX mode
            device.Status = DeviceStatusEnum.RXMode;
            IsRunning = true;
        }


        /// <summary>
        /// send data target
        /// </summary>
        /// <param name="buffer">data buffer</param>
        /// <param name="timeOut">time out in millium seconds</param>
        /// <returns></returns>
        public bool Send(byte[] buffer, int timeOut=1000)
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Please call [Start] method before send data");
            }
            //lock (syncRoot)
            //{
                byte[] sendBuffer = new byte[32];
                int bytesLeft = buffer.Length;
                int sendPos = 0;
                bool result = true;
                isMaxRT = false;
                isSending = true;
                int length = buffer.Length;
                
                //stop listening    
                device.Status = DeviceStatusEnum.StandBy;

                //clear send buffer before we send data
                device.FlushTX();

                //set device to Tx mode, this causes device into Standby-II mode
                device.Status = DeviceStatusEnum.TXMode; 

                //split data into 32 bytes buffer , send them and wait for ACK signal
                while (bytesLeft>0)
                {
                    
                    //build send buffer
                    int sendBufferLength = System.Math.Min(bytesLeft, 32);
                    if (sendBufferLength!=sendBuffer.Length)
                    {
                        sendBuffer = new byte[sendBufferLength];
                    }
                    Array.Copy(buffer, sendPos, sendBuffer, 0, sendBufferLength);

                    //move the pointers
                    sendPos += sendBufferLength;
                    bytesLeft -= sendBufferLength;

                    
                    //send data to TX pipe
                    writer.Write(sendBuffer);

                    //reset wait handler
                    sendCompleteEvent.Reset();

                    //send and wait for ACK signal
                    if (sendCompleteEvent.WaitOne(timeOut, false) && !isMaxRT) 
                    {
                        //send success, do nothing
                    }
                    else
                    {
                        //send failed
                        result = false;
                        break;
                    }
                }
                device.Status = DeviceStatusEnum.StandBy;
                device.Status = DeviceStatusEnum.RXMode; // set the device back to RX mode
                isSending = false;
                return result;
            //}
        }

        void device_OnInterrupt(Register.Register_STATUS status)
        {
            if (status.MAX_RT)
            {
                isMaxRT = true;
                sendCompleteEvent.Set();
            }
            if (status.TX_DS)
            {
                //isMaxRT = false;
                sendCompleteEvent.Set();
            }
            device.Status = DeviceStatusEnum.StandBy;
            if (status.RX_DR)
            {
                if (DataArrived!=null)
                {
                    DataArrived(reader.ReadBufferAll());
                }
            }


            status.Save(); //clear status flag

            if (!isSending)// continue listening if we're not sending things
            {
                device.Status = DeviceStatusEnum.RXMode;
            }
        }

        public void Stop()
        {
            if (!IsRunning)
            {
                return;
            }
            device.OnInterrupt -= device_OnInterrupt;
            device.Status = DeviceStatusEnum.StandBy;
            device.Status = DeviceStatusEnum.PowerDown;
            IsRunning = false;
            
        }

    }
}
